科技新知

前面的例子,我們已經成功設定 ingress Network,也加了 virtual ip 。如果大家的目標是單一 web 應用,應該就已經很足夠。但作為一個足夠節儉的老闆,怎會讓一個 Swarm 只跑一個 Web 應用?但問題來了,一個 docker swarm service 就已經佔用一個公開端口 (例如上述的8888,或是更常見的443)。怎麼可以做到多個 service 分享同一個端口?答案就是回到傳統的 Web Server 當中,使用它們的 virtual host 及 proxy 功能,以達到這一效果。我們就以 Nginx 為例,去建立一個守門口的網關 (gateway) 。

以下就是一個最簡單的例子,最前端的 http-gateway (nginx) 對外公開端口 8080 ,它根據 virtual host,去分派對應的請求去 dmzhttp (bretfisher/httpenv) 及 managerhttp (bretfisher/httpenv) 。構架圖就是以下這樣。

                              ┌───────────┐  
              ┌──────────────►│  dmzhttp  │  
              │               └───────────┘  
              │                              
           ┌───────────────┐                 
           │ http-gateway  │                 
  ────────►│ (nginx:8080)  │                 
           └──┬────────────┘                 
              │                              
              │              ┌─────────────┐ 
              └─────────────►│ managerhttp │ 
                             └─────────────┘ 
 

換成 docker stack ,就大概如下

services:
  http-gateway:
    image: http-gatewayports:
      - 8080:8080deploy:
      replicas: 1update_config:
        delay: 10srestart_policy:
        condition: on-failuredmzhttp:
    image: bretfisher/httpenvdeploy:
      replicas: 2update_config:
        delay: 10srestart_policy:
        condition: on-failuremanagerhttp:
    image: bretfisher/httpenvdeploy:
      replicas: 3update_config:
        delay: 10srestart_policy:
        condition: on-failure
 

docker stack有一個很好的功能,就是 service 名會自動成為同一段網絡中的 hostname 。即是http-gateway中,它可以經DNS,找到 dmzhttp 、 managerhttp,也就是它的 nginx 可以設定成如下的樣子。

# default.conf
server {
	listen       8080;
	listen  [::]:8080;
	server_name  managerhttp;
	resolver 127.0.0.11 valid=30s;

	location ^~ / {
		set $upstream_manager managerhttp;
		proxy_cache off;
		proxy_pass http://$upstream_manager:8888$request_uri;
	}
}
server {
	listen       8080;
	listen  [::]:8080;
	server_name  dmzhttp;
	resolver 127.0.0.11 valid=30s;

	location ^~ / {
		set $upstream_dmz dmzhttp;
		proxy_cache off;
		proxy_pass http://$upstream_dmz:8888$request_uri;
	}
}
 

上面的例子中,就是一般的 virtual host + nginx proxy 設定。特別要說明的是 resolver 那一行,它指向 docker DNS (127.0.0.11), 而且還可以讓nginx在找不到上游時,不要馬上死亡。這樣 docker swarm 中各個 service 隨時加加減減,有保命的作用。

最後我們的 http-gateway 就是 nginx image + default.conf 上述的 docker 就可以用以下方式打包。

# Dockerfile
# docker image build -t http-gateway ./
FROM nginx:latest
COPY default.conf /etc/nginx/conf.d/default.conf
 

上面的 docker stack 和 nginx config,只要同步增加 service 及對應的 proxy pass,就可以o讓同一個端口,根據不同hostname做分流。當然,如果大家可以共用端口及 hostname 也可以,分流就改用 nginx location 來設定,不過這是更加偏向 nginx 的內容,日後有機會再介紹。本篇就先集中於 docker 相關的議題。

在安全性的角度, docker 還有一些配置可以做,就是讓 dmzhttp 和 managerhttp 在不同的機器上發佈。假設我們的網絡分開兩段,一段是 manager 專用,一段是 dmz 專用。在建立 docker swarm 後,我們可以為不同的節點加入對應的標簽。

docker node update --label-add zone=manager YOUR_MANAGER_NODE
docker node update --label-add zone=dmz YOUR_DMZ_NODE
 

然後我們通過修改 docker stakc 中的 placement -> constraints ,限制不同的 service 在不同的節點上運行。

services:
  http-gateway:
    image: http-gateway
    ports:
      - 8080:8080
    deploy:
      replicas: 1
      update_config:
        delay: 10s
      restart_policy:
        condition: on-failure
  dmzhttp:
    image: bretfisher/httpenv
    deploy:
      replicas: 2
      update_config:
        delay: 10s
      restart_policy:
        condition: on-failure
+     placement:+       constraints:+         - node.labels.zone==dmz
  managerhttp:
    image: bretfisher/httpenv
    deploy:
      replicas: 3
      update_config:
        delay: 10s
      restart_policy:
        condition: on-failure
+     placement:+       constraints:+         - node.labels.zone==manager
 

使用上面的例子,我們就可以達到簡單分離的效果。但大家緊記,這個分離效果始終是一個規則式功能,它與防火牆的隔離還是有本質上的區別。除了利用傳統的防火牆技術外,我們的docker swarm network,其實也可以做更多隔離,我們日後再慢慢加強這個例子。

馬交野